package com.fizzgate.service;

import com.alibaba.fastjson.JSON;
import com.alipay.sofa.ark.api.ClientResponse;
import com.alipay.sofa.ark.spi.constant.Constants;
import com.alipay.sofa.ark.spi.model.BizInfo;
import com.alipay.sofa.ark.spi.model.BizOperation;
import com.alipay.sofa.ark.spi.model.BizState;
import org.slf4j.LoggerFactory;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;
import com.alipay.sofa.ark.api.ArkClient;
import com.alipay.sofa.ark.spi.model.Biz;
import com.fizzgate.config.FizzMangerConfig;
import org.apache.commons.io.FileUtils;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;

import java.io.IOException;
import java.nio.file.Paths;
import java.io.File;
import java.net.URL;

import java.util.*;
import java.util.stream.Collectors;
import org.slf4j.Logger;

@Service
public class PluginService {
    private static final Logger LOGGER = LoggerFactory.getLogger(PluginService.class);
    @Value("${biz.file.node:biz/jar}")
    private String bizDir;

    @Value("${biz.file.url-path:/api/fizz-manager/extension/dynamic/list}")
    private String bizPackgeUri;

    @Value("${biz.file.download-path:/api/fizz-manager/extension/download}")
    private String bizDowloadUri;

    @Resource
    private FizzMangerConfig fizzMangerConfig;
    private Map<String, BizFileStatus> BIZ_FILES_STATUS = new HashMap<>();
    public Map<String, Biz>  STARTED_BIZS = new HashMap<>();

    private String finalBizDir ;


    public String buildBizDir(String bizDir) {
        String folder = null;
        if (Paths.get(bizDir).isAbsolute()) {
            folder = bizDir ;
        } else {
            String path = System.getProperty("user.dir");
            folder = path + "/" + bizDir + "/" ;
        }
        return folder;
    }
    public void postInit() {
        finalBizDir = buildBizDir(bizDir);
        initBizStatus();

    }

    public void closeAndRemoveBiz(String filename){
        Biz biz = this.STARTED_BIZS.get(filename);
        if (biz != null){
            try {
                ArkClient.uninstallBiz(biz.getBizName(), biz.getBizVersion());
                File file = new File(finalBizDir+filename);
                if (file.exists()){
                    file.delete();
                }
                this.STARTED_BIZS.remove(filename);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }

    }

    private void initBizStatus(){
        BIZ_FILES_STATUS = new HashMap<>();
        File dir =  new File(finalBizDir);
        if (!dir.exists()){
            dir.mkdirs();
        }
        Collection<File> bizFiles = FileUtils.listFiles(new File(finalBizDir), new String[]{"jar"}, false);
        bizFiles.forEach(file->{
            BIZ_FILES_STATUS.put(file.getName(), BizFileStatus.DOWNLOADED);
        });
    }
    @SuppressWarnings("unchecked")
    public List<Map> getBizs(){
        String managerUrl = fizzMangerConfig.managerUrl;
        WebClient webClient = WebClient.create(managerUrl);
        Map errorMsg = new HashMap<String, Object>();
        Map result = (Map) webClient.get()
                .uri(bizPackgeUri)
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<Map<String,Object>>(){})
                .doOnError(WebClientResponseException.class, err -> {
                    errorMsg.put("msg",  err.getLocalizedMessage());
                    LOGGER.error("failed to get bizs list,{}", err.getLocalizedMessage());
                    throw new RuntimeException(err.getResponseBodyAsString());
                })
                .onErrorReturn(errorMsg)
                .block();

        if (result!=null&& result.get("code") != null && (Integer)result.get("code") == 200){
            if (result.get("data") != null){
                LOGGER.info("success to get biz data:{}", JSON.toJSONString(result.get("data")));
                List data = (List) result.get("data");
                data = (List) data.stream().filter(it-> it instanceof Map).collect(Collectors.toList());;
                return (List<Map>)data;
            } else {
                return null;
            }
        } else {
            LOGGER.error("failed request: {}{} to get bizs list", managerUrl,bizPackgeUri);
            return null;
        }
    }

    public void downloadBizs(List<Map> bizs){
        if (bizs==null)
            return;
        String managerUrl = fizzMangerConfig.managerUrl;
        List<Mono<Object>> donwloadTasks =  bizs.stream().filter(biz->{
            // check whether local file exists
            String filename = (String) biz.get("filename");
            if (isBizFileExists(filename)){
                return false;
            }
            return true;
        }).collect(Collectors.toList()).stream().map(biz->{
            String filename = (String) biz.get("filename");
            String url =  managerUrl+ bizDowloadUri + "?filename="+ filename;
            return downloadBiz(filename, url);
        }).collect(Collectors.toList());
        Flux<Object> flux = Flux.merge(donwloadTasks);
        flux.collectList().block();
    }

    // close biz not in the list
    public void closeBizs(List<Map> bizs){
        List<String>onlineFilenames = bizs.stream().map(bizDic->{
            String filename = (String) bizDic.get("filename");
            return filename;
        }).collect(Collectors.toList());
        Collection<File> bizFiles = FileUtils.listFiles(new File(finalBizDir), new String[]{"jar"}, false);
        bizFiles.stream().forEach(bizFile->{
            if (!onlineFilenames.contains(bizFile.getName())){
                // 有启动的直接关闭
                Biz biz = STARTED_BIZS.get(bizFile.getName());
                if (biz != null){
                    try {
                        // 关闭biz
                        ArkClient.uninstallBiz(biz.getBizName(), biz.getBizVersion());
                    } catch (Throwable e) {
                        throw new RuntimeException(e);
                    }
                }
                // 线上没有找到的需要删除文件
                if (bizFile.exists()){
                    bizFile.delete();
                }
            }
        });
    }

    public void startBizs(List<Map> bizs) {
        // 获取在list启动文件；
        bizs.forEach(bizDic->{
            startBiz(bizDic);
        });
    }
    private void startBiz(Map bizDic) {
        String filename = (String) bizDic.get("filename");
        if (isBizFileExists(filename)){
            File file = new File(finalBizDir+filename);
            BizOperation bizOperation = new BizOperation()
                    .setOperationType(BizOperation.OperationType.INSTALL);
//                    String param = parameters.toArray(new String[] {})[0];
            String param = "file://"+file.getPath();
            try {
                bizOperation.putParameter(Constants.CONFIG_BIZ_URL, param);
            } catch (Throwable t) {
                String[] nameAndVersion = param.split(Constants.STRING_COLON);
                if (nameAndVersion.length != 2) {
                    LOGGER.error(
                            "Invalid telnet biz install command {}", param);
                    return;
                }
                bizOperation.setBizName(nameAndVersion[0]).setBizVersion(
                        nameAndVersion[1]);
            }
            Biz biz;

            boolean force = true;
            Biz existedBiz =  STARTED_BIZS.get(filename);
            if (existedBiz != null){
                if (!force){
                    return ;
                }

                biz = ArkClient.getBizManagerService().getBiz(existedBiz.getBizName(),existedBiz.getBizVersion());

                if (biz !=null && (biz.getBizState() == BizState.ACTIVATED)){
                    try {
                        ArkClient.uninstallBiz(biz.getBizName(), biz.getBizVersion());
                    } catch (Throwable throwable) {
                        LOGGER.error(
                                "Fail to process telnet uninstall command: " + param, throwable);
                    }
                    LOGGER.info(
                            "unintallBiz bizName:{}, bizVersion: {}", biz.getBizName(),biz.getBizVersion());
                }

            }

            try {
                LOGGER.info("start to install and run biz {}",filename);
                ClientResponse response  = ArkClient.installOperation(bizOperation);
                Iterator iterator = response.getBizInfos().iterator();

                if (iterator.hasNext()) {
                    BizInfo info = (BizInfo) iterator.next();
                    biz =  ArkClient.getBizManagerService().getBiz (info.getBizName() ,info.getBizVersion());
                    STARTED_BIZS.put(filename, biz);
                    LOGGER.info("finish to install and run biz {}, please check the status later",filename);
                } else {
                    LOGGER.error("uanble to find started biz : {}", filename);
                }
            } catch (Throwable throwable) {
                LOGGER.error(
                        "Fail to process install command: " + param, throwable);
            }

        }
    }


    private boolean isBizFileExists(String filename) {
        File bizFile = new File(finalBizDir + filename );
        return bizFile.exists();
    }

    private Mono<Object> downloadBiz(String filename, String downloadUrl) {
        return Mono.fromRunnable(()-> {
            try {
                String bizFileUrl =	finalBizDir + filename;
                File file = new File(bizFileUrl);
                if (file.exists()){
                    file.delete();
                }

                LOGGER.info("start to download biz filename:{}", filename);
                BIZ_FILES_STATUS.put(filename, BizFileStatus.DOWNLOADING);
                FileUtils.copyURLToFile(new URL(downloadUrl), file);
                BIZ_FILES_STATUS.put(filename, BizFileStatus.DOWNLOADED);
                LOGGER.info("success to download biz filename:{}", filename);


            } catch ( IOException e) {
                LOGGER.error("failed to download biz filename:{} , error:{}", filename, e.getMessage());
            }
        });
    }



}
